package deepbrain.iit.demo;

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import deepbrain.iit.PhiCompResult;
import deepbrain.iit.PhiComputation;
import deepbrain.simnetwork.exception.SimNetworkRuntimeException;
import deepbrain.simnetwork.loader.NetworkLoader;
import deepbrain.simnetwork.loader.xml.XmlNetworkLoader;
import deepbrain.simnetwork.mechanism.Mechanism;
import deepbrain.simnetwork.network.Network;
import deepbrain.simnetwork.network.NetworkState;
import deepbrain.simnetwork.structure.Connection;
import deepbrain.simnetwork.util.SimNetUtils;

public class IITDemo {

	public static final void compPHIforState(String networkFilePath,
			NetworkState state, Mechanism mechanism) {
		compPHIforState(networkFilePath, state, mechanism, 2);
	}

	public static final void compPHIforState(String networkFilePath,
			NetworkState state, Mechanism mechanism, int n_part) {
		try {
			NetworkLoader networkLoader = new XmlNetworkLoader(networkFilePath);
			Network network = networkLoader.loadNetwork();
			if (mechanism != null)
				network.setMechanism(mechanism);

			PhiComputation phi = new PhiComputation(network);
			System.out.println(state);
			System.out.println(phi.findMIP(state, n_part));
		} catch (Exception e) {
			throw new SimNetworkRuntimeException(e);
		}
	}

	public static final void compPHIforStates(String networkFilePath,
			Collection<NetworkState> states, Mechanism mechanism) {
		compPHIforStates(networkFilePath, states, mechanism, 2);
	}

	public static final void compPHIforStates(String networkFilePath,
			Collection<NetworkState> states, Mechanism mechanism, int n_part) {
		try {
			NetworkLoader networkLoader = new XmlNetworkLoader(networkFilePath);
			Network network = networkLoader.loadNetwork();
			if (mechanism != null)
				network.setMechanism(mechanism);

			PhiComputation phi = new PhiComputation(network);
			for (NetworkState state : states) {
				System.out.println(state);
				System.out.println(phi.findMIP(state, n_part));
			}

		} catch (Exception e) {
			throw new SimNetworkRuntimeException(e);
		}
	}

	public static final void compPHIforAllStates(String networkFilePath,
			Mechanism mechanism) {
		long startTime = System.currentTimeMillis();
		compPHIforAllStates(networkFilePath, mechanism, 2);
		System.out.println("Used time: "
				+ (System.currentTimeMillis() - startTime) + "ms");
	}

	public static final void compPHIforAllStates(String networkFilePath,
			Mechanism mechanism, int n_part) {
		try {
			NetworkLoader networkLoader = new XmlNetworkLoader(networkFilePath);
			Network network = networkLoader.loadNetwork();
			if (mechanism != null)
				network.setMechanism(mechanism);

			List<NetworkState> states = SimNetUtils
					.getAllCertainNetworkStates(network.getNumOfNodes());

			PhiComputation phi = new PhiComputation(network);
			for (NetworkState state : states) {
				System.out.println(state);
				System.out.println(phi.findMIP(state, n_part));
			}

		} catch (Exception e) {
			throw new SimNetworkRuntimeException(e);
		}
	}

	/**
	 * Compute all possible Networks given the node number and calculate their
	 * PHI in all possible states
	 * 
	 * @author QiaoYan
	 * @create 2009-4-2
	 */
	public static final void compPHIforAllNetConAllStates(
			String networkFilePath, Mechanism mechanism, int n_part) {
		try {
			/* load network from xml file */
			NetworkLoader networkLoader = new XmlNetworkLoader(networkFilePath);
			Network network = networkLoader.loadNetwork();
			if (mechanism != null)
				network.setMechanism(mechanism);

			/* initialize */
			int n_nodes = network.getNumOfNodes();
			long n_max_combination = 1; // the number of different networks
			for (int i = 1; i <= n_nodes * n_nodes; i++)
				n_max_combination *= 2;
			StatisticInfo stat = new StatisticInfo(n_nodes);
			FileOutputStream fout = new FileOutputStream(
					"outputs/TestNetSizeN.out");
			DataOutputStream dout = new DataOutputStream(fout);
			dout.writeBytes("network size:" + n_nodes + "\r\n");
			dout
					.writeBytes("Network_ID\tN_edges\tvalid_states\tavg_PHI\tvar_PHI\r\n");

			// main loop: for different connections:
			for (long com = 0; com < n_max_combination; com++) {
				if (IsValidNet(com, n_nodes)) {
					stat.n_valid_net++;
					stat.cur_net_ID = com;
					stat.nextNet();
				} else
					continue;

				int power = (int) (Math.log(com) / Math.log(2));
				if (power < 0)
					power = 0;

				int source, dest; // power=source*n_nodes+dest;
				long temp_com = com;

				while (temp_com > 0) {
					if (temp_com >= (1 << power)) {// edge exists when
						// com.bitAt(power)==1
						source = power / n_nodes;
						dest = power - source * n_nodes;
						network.addConnection(new Connection<Integer>(source,
								dest));
						// System.out.println("Com="+com+": add edge"+source+", "+
						// dest+"	power="+power);
						stat.cur_net_n_edges++;
						temp_com -= (1 << power);
					}
					power--; // decrement
				}// now a new network has been constructed

				/* compute all possible states and calculate PHI */
				List<NetworkState> states = SimNetUtils
						.getAllCertainNetworkStates(network.getNumOfNodes());

				PhiComputation phi = new PhiComputation(network);
				for (NetworkState state : states) {
					// System.out.println(state);
					PhiCompResult result = phi.findMIP(state, n_part);
					if (result.MIPs.size() == 0)
						continue;
					stat.cur_valid_states++;
					stat.cur_list_PHI.add(result.PHI);
					stat.cur_cumu_PHI += result.PHI;
				}
				stat.calcuVar();
				// print statistic info:
				dout.writeBytes(Integer.toBinaryString((int) stat.cur_net_ID)
						+ "\t" + stat.cur_net_n_edges + "\t"
						+ stat.cur_valid_states + "\t" + stat.cur_cumu_PHI
						/ stat.cur_valid_states + "\t" + stat.cur_var_PHI
						+ "\r\n");
				/*
				 * System.out.println(Integer.toBinaryString((int)stat.cur_net_ID
				 * )+ "	"+stat.cur_net_n_edges+ "	"+stat.cur_valid_states+
				 * "	"+stat.cur_cumu_PHI/stat.cur_valid_states);
				 */
				/* restore the network */
				network.removeAllConnections();
			}
			System.out.println("Total valid network:" + stat.n_valid_net);

		} catch (Exception e) {
			throw new SimNetworkRuntimeException(e);
		}
	}

	/*
	 * Networks with isolated nodes are invalid Used specifically by function:
	 * compPHIforAllNetConAllStates() only support n==3& n==4, to be improved
	 */
	private static boolean IsValidNet(long combinationN, int n_nodes) {
		/* Check isolated nodes */
		if (n_nodes == 3) {
			if (0 == (combinationN & 0116) || 0 == (combinationN & 0252)
					|| 0 == (combinationN & 0344))
				return false;
		} else if (n_nodes == 4) {
			if (0 == (combinationN & 0x111E) || 0 == (combinationN & 0x22D2)
					|| 0 == (combinationN & 0x4B44)
					|| 0 == (combinationN & 0x7888))
				return false;
		}
		/* Check self-to-self edge */
		int mark = 0x1;
		for (int i = 0; i < n_nodes; i++) {
			if (0 != (combinationN & mark))
				return false;
			mark = mark << (n_nodes + 1);
		}

		return true;
	}

}

/**
 * Class: StatisticInfo To collect statistic info for each network that have n
 * nodes
 * 
 * @author QiaoYan
 * @create 2009-4-2
 */
class StatisticInfo {
	public int n_nodes;
	public int n_valid_net = 0;

	public long cur_net_ID;
	public int cur_net_n_edges = 0;
	public int cur_valid_states = 0;
	public double cur_cumu_PHI = 0;
	public double cur_var_PHI = 0; // variance
	public ArrayList<Double> cur_list_PHI;

	public StatisticInfo(int n_nodes) {
		this.n_nodes = n_nodes;
		n_valid_net = 0;
		cur_list_PHI = new ArrayList<Double>();
	}

	public void nextNet() {
		cur_net_n_edges = 0;
		cur_valid_states = 0;
		cur_cumu_PHI = 0;
		cur_var_PHI = 0;
		cur_list_PHI.clear();
	}

	public void calcuVar() {
		if (this.cur_valid_states == 0 || this.cur_cumu_PHI == 0)
			return;
		double avg_PHI = this.cur_cumu_PHI / this.cur_valid_states;
		for (int i = 0; i < this.cur_list_PHI.size(); i++) {
			this.cur_var_PHI += (this.cur_list_PHI.get(i) - avg_PHI)
					* (this.cur_list_PHI.get(i) - avg_PHI);
		}
		this.cur_var_PHI = Math.sqrt(this.cur_var_PHI);
	}
}
